package com.iocup.keybastion.authentication.authenticator;


import com.github.sd4324530.jtuple.Tuple2;
import com.iocup.keybastion.authentication.TokenService;
import com.iocup.keybastion.authentication.userdetails.UserDetails;
import com.iocup.keybastion.authentication.userdetails.password.PasswordVerifiers;
import com.iocup.keybastion.authentication.userdetails.service.UserDetailService;
import com.iocup.keybastion.common.AuthConstant;
import com.iocup.keybastion.context.SecurityContextHolder;
import com.iocup.keybastion.context.WebContextHolder;
import com.iocup.keybastion.core.profile.UserProfile;
import com.iocup.keybastion.core.session.Session;
import com.iocup.keybastion.exception.AuthenticationException;
import com.iocup.keybastion.utils.AuthUtils;
import com.iocup.keybastion.utils.NameUtils;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;

import java.util.Optional;

/**
 *
 * @author xyjxust
 * @create 2022/3/8 13:56
 **/
public abstract class AbstractAuthenticator<T extends LoginCredentials> implements Authenticator<T> {


    @Setter
    protected TokenService tokenService;
    @Setter
    protected UserDetailService userDetailService;
    @Setter
    protected PasswordVerifiers passwordVerifiers;

    @Override
    public Optional<T> getCredentials(WebContextHolder webContextHolder) {
        //抽取请求中的token
        Optional<T> optionalT = doGetCredentials(webContextHolder);
        //如果存在
        if (optionalT.isPresent()) {
            T credentials = optionalT.get();
            //设置认证类型
            credentials.setAuthType(webContextHolder.getRequestHeader(AuthConstant.AUTH_TYPE, ""));
            //设置登录类型
            credentials.setDevice(NameUtils.getDevice(webContextHolder));
            return optionalT;
        }
        return Optional.empty();
    }

    public abstract Optional<T> doGetCredentials(WebContextHolder webContextHolder);


    @Override
    public Optional<Tuple2<UserProfile, Session>> authenticate(T credentials, WebContextHolder webContextHolder) {
        if (credentials == null) {
            return Optional.empty();
        }
        //通过token获取用户配置信息
        Optional<UserDetails> optionalUserDetails = doAuthenticate(credentials, webContextHolder);
        UserProfile userProfile;
        //如果用户信息获取到了
        if (optionalUserDetails.isPresent()) {
            //校验用户信息是否合理
            validateUserDetail(credentials, optionalUserDetails.get());
            //转化为userProfile类
            userProfile = AuthUtils.buildUserProfile(optionalUserDetails.get());
            userProfile.setDevice(credentials.getDevice());
            //创建登录session
            Session session = this.tokenService.createAndSave(userProfile);
            //放入上下文中
            SecurityContextHolder.getContext().setToken(session.getToken());
            SecurityContextHolder.getContext().setUserProfile(userProfile);
            return Optional.ofNullable(Tuple2.with(userProfile, session));
        }
        return Optional.empty();
    }

    /**
     * 通过token获取用户配置信息
     * @param credentials
     * @param webContextHolder
     * @return
     */
    public abstract Optional<UserDetails> doAuthenticate(T credentials, WebContextHolder webContextHolder);

    /**
     * 校验用户信息
     * @param credentials token信息
     * @param userDetails 用户信息
     */
    protected void validateUserDetail(T credentials, UserDetails userDetails) {
        if (userDetails == null || StringUtils.isEmpty(userDetails.getLoginName()) || StringUtils.isEmpty(userDetails.getPassword())) {
            throw new AuthenticationException("用户名或密码错误");
        }
        //校验密码
        boolean result = passwordVerifiers.verifiers(
                userDetails.getPassword(), credentials.getPassword());
        if (!result) {
            throw new AuthenticationException("用户名或密码错误");
        }
        if (!userDetails.isEnabled()) {
            throw new AuthenticationException("账户已禁用");
        }
        if (!userDetails.isAccountNonLocked()) {
            throw new AuthenticationException("账户已被锁");
        }
        if (!userDetails.isAccountNonExpired()) {
            throw new AuthenticationException("账户已过期");
        }
    }
}
